/* 
 * AP(r) Computer Science GridWorld Case Study:
 * Copyright(c) 2002-2006 College Entrance Examination Board 
 * (http://www.collegeboard.com).
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * @author Julie Zelenski
 * @author Chris Nevison
 * @author Cay Horstmann
 */

package info.gridworld.gui;

import info.gridworld.grid.Grid;
import info.gridworld.grid.Location;
import info.gridworld.world.World;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;

import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * The WorldFrame displays a World and allows manipulation of its occupants.
 * <br />
 * This code is not tested on the AP CS A and AB exams. It contains GUI
 * implementation details that are not intended to be understood by AP CS
 * students.
 */
public class WorldFrame<T> extends JFrame
{
    private GUIController<T> control;
    private GridPanel display;
    private JTextArea messageArea;
    private ArrayList<JMenuItem> menuItemsDisabledDuringRun;
    private World<T> world;
    private ResourceBundle resources;
    private DisplayMap displayMap;
    private boolean first = true;
    private Set<Class> gridClasses;
    private JMenu newGridMenu;

    private static int count = 0;

    /**
     * Constructs a WorldFrame that displays the occupants of a world
     * @param world the world to display
     */
    public WorldFrame(World<T> world)
    {
        this.world = world;
        count++;
        resources = ResourceBundle
                .getBundle(getClass().getName() + "Resources");

        try
        {
            System.setProperty("sun.awt.exception.handler",
                    GUIExceptionHandler.class.getName());
        }
        catch (SecurityException ex)
        {
            // will fail in an applet
        }

        addWindowListener(new WindowAdapter()
        {
            public void windowClosing(WindowEvent event)
            {
                count--;
                if (count == 0)
                    System.exit(0);
            }
        });

        displayMap = new DisplayMap();
        String title = System.getProperty("info.gridworld.gui.frametitle");
        if (title == null) title = resources.getString("frame.title"); 
        setTitle(title);
        setLocation(25, 15);

        URL appIconUrl = getClass().getResource("GridWorld.gif");
        ImageIcon appIcon = new ImageIcon(appIconUrl);
        setIconImage(appIcon.getImage());
        
        makeMenus();

        JPanel content = new JPanel();
        content.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
        content.setLayout(new BorderLayout());
        setContentPane(content);

        display = new GridPanel(displayMap, resources);

        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new
           KeyEventDispatcher() 
           {
               public boolean dispatchKeyEvent(KeyEvent event)
               {
                   if (getFocusOwner() == null) return false;
                   String text = KeyStroke.getKeyStrokeForEvent(event).toString();
                   final String PRESSED = "pressed ";                  
                   int n = text.indexOf(PRESSED);
                   if (n < 0) return false;
                   // filter out modifier keys; they are neither characters or actions
                   if (event.getKeyChar() == KeyEvent.CHAR_UNDEFINED && !event.isActionKey()) 
                       return false;
                   text = text.substring(0, n)  + text.substring(n + PRESSED.length());
                   boolean consumed = getWorld().keyPressed(text, display.getCurrentLocation());
                   if (consumed) repaint();
                   return consumed;
               }
           });
        
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewport(new PseudoInfiniteViewport(scrollPane));
        scrollPane.setViewportView(display);
        content.add(scrollPane, BorderLayout.CENTER);

        gridClasses = new TreeSet<Class>(new Comparator<Class>()
        {
            public int compare(Class a, Class b)
            {
                return a.getName().compareTo(b.getName());
            }
        });
        for (String name : world.getGridClasses())
            try
            {
                gridClasses.add(Class.forName(name));
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }

        Grid<T> gr = world.getGrid();
        gridClasses.add(gr.getClass());

        makeNewGridMenu();

        control = new GUIController<T>(this, display, displayMap, resources);
        content.add(control.controlPanel(), BorderLayout.SOUTH);

        messageArea = new JTextArea(4, 35);
        messageArea.setEditable(false);
        messageArea.setFocusable(false);
        messageArea.setBackground(new Color(0xFAFAD2));
        content.add(new JScrollPane(messageArea), BorderLayout.NORTH);

        pack();
        repaint(); // to show message
        display.setGrid(gr);
    }

    public void repaint()
    {
        String message = getWorld().getMessage();
        if (first){
            //message = resources.getString("message.default");
        	message = "Click Play to Play.\nPause to Pause.\nMakeTurrets and kill the enemys before they reach the end";
        	first =false;
        }
        messageArea.setText(message);
        messageArea.repaint();
        display.repaint(); // for applet
        super.repaint();
    }

    /**
     * Gets the world that this frame displays
     * @return the world
     */
    public World<T> getWorld()
    {
        return world;
    }

    /**
     * Sets a new grid for this world. Occupants are transferred from
     * the old world to the new.
     * @param newGrid the new grid
     */
    public void setGrid(Grid<T> newGrid)
    {
        Grid<T> oldGrid = world.getGrid();
        Map<Location, T> occupants = new HashMap<Location, T>();
        for (Location loc : oldGrid.getOccupiedLocations())
            occupants.put(loc, world.remove(loc));

        world.setGrid(newGrid);
        for (Location loc : occupants.keySet())
        {
            if (newGrid.isValid(loc))
                world.add(loc, occupants.get(loc));
        }

        display.setGrid(newGrid);
        repaint();
    }

    /**
     * Displays an error message
     * @param t the throwable that describes the error
     * @param resource the resource whose .text/.title strings 
     * should be used in the dialog
     */
    public void showError(Throwable t, String resource)
    {
        String text;
        try
        {
            text = resources.getString(resource + ".text");
        }
        catch (MissingResourceException e)
        {
            text = resources.getString("error.text");
        }

        String title;
        try
        {
            title = resources.getString(resource + ".title");
        }
        catch (MissingResourceException e)
        {
            title = resources.getString("error.title");
        }

        String reason = resources.getString("error.reason");
        String message = text + "\n"
                + MessageFormat.format(reason, new Object[]
                    { t });

        JOptionPane.showMessageDialog(this, message, title,
                JOptionPane.ERROR_MESSAGE);
    }

    // Creates the drop-down menus on the frame.

    private JMenu makeMenu(String resource)
    {
        JMenu menu = new JMenu();
        configureAbstractButton(menu, resource);
        return menu;
    }

    private JMenuItem makeMenuItem(String resource, ActionListener listener)
    {
        JMenuItem item = new JMenuItem();
        configureMenuItem(item, resource, listener);
        return item;
    }

    private void configureMenuItem(JMenuItem item, String resource,
            ActionListener listener)
    {
        configureAbstractButton(item, resource);
        item.addActionListener(listener);
        try
        {
            String accel = resources.getString(resource + ".accel");
            String metaPrefix = "@";
            if (accel.startsWith(metaPrefix))
            {
                int menuMask = getToolkit().getMenuShortcutKeyMask();
                KeyStroke key = KeyStroke.getKeyStroke(KeyStroke.getKeyStroke(
                        accel.substring(metaPrefix.length())).getKeyCode(),
                        menuMask);
                item.setAccelerator(key);
            }
            else
            {
                item.setAccelerator(KeyStroke.getKeyStroke(accel));
            }
        }
        catch (MissingResourceException ex)
        {
            // no accelerator
        }
    }

    private void configureAbstractButton(AbstractButton button, String resource)
    {
        String title = resources.getString(resource);
        int i = title.indexOf('&');
        int mnemonic = 0;
        if (i >= 0)
        {
            mnemonic = title.charAt(i + 1);
            title = title.substring(0, i) + title.substring(i + 1);
            button.setText(title);
            button.setMnemonic(Character.toUpperCase(mnemonic));
            button.setDisplayedMnemonicIndex(i);
        }
        else
            button.setText(title);
    }

    private void makeMenus()
    {
        JMenuBar mbar = new JMenuBar();
        JMenu menu;

        menuItemsDisabledDuringRun = new ArrayList<JMenuItem>();

        mbar.add(menu = makeMenu("menu.file"));

        newGridMenu = makeMenu("menu.file.new");
        menu.add(newGridMenu);
        menuItemsDisabledDuringRun.add(newGridMenu);

        menu.add(makeMenuItem("menu.file.quit", new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                System.exit(0);
            }
        }));

        mbar.add(menu = makeMenu("menu.view"));

        menu.add(makeMenuItem("menu.view.up", new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
            	turretInfo();
            }
        }));

        mbar.add(menu = makeMenu("menu.help"));
        menu.add(makeMenuItem("menu.help.help", new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                showHelp();
            }
        }));
        menu.add(makeMenuItem("menu.help.license", new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                showLicense();
            }
        }));

        setRunMenuItemsEnabled(true);
        setJMenuBar(mbar);
    }

    private void makeNewGridMenu()
    {
        newGridMenu.removeAll();
        MenuMaker<T> maker = new MenuMaker<T>(this, resources, displayMap);
        maker.addConstructors(newGridMenu, gridClasses);
    }

    /**
     * Sets the enabled status of those menu items that are disabled when
     * running.
     * @param enable true to enable the menus
     */
    public void setRunMenuItemsEnabled(boolean enable)
    {
        for (JMenuItem item : menuItemsDisabledDuringRun)
            item.setEnabled(enable);
    }

    /**
     * Brings up a simple dialog with some general information.
     */
    private void showAboutPanel()
    {
        String html = MessageFormat.format(resources
                .getString("dialog.about.text"), new Object[]
            { resources.getString("version.id") });
        String[] props = { "java.version", "java.vendor", "java.home", "os.name", "os.arch", "os.version", "user.name", "user.home", "user.dir" };
        html += "<table border='1'>";
        for (String prop : props)
        {
            try
            {
                String value = System.getProperty(prop);
                html += "<tr><td>" + prop + "</td><td>" + value + "</td></tr>";
            }
            catch (SecurityException ex)
            {
                // oh well...
            }           
        }
        html += "</table>";
        html = "<html>" + html + "</html>";
        JOptionPane.showMessageDialog(this, new JLabel(html), resources
                .getString("dialog.about.title"),
                JOptionPane.INFORMATION_MESSAGE);
    }

    /**
     * Brings up a window with a scrolling text pane that display the help
     * information.
     */
    private void turretInfo()
    {
        JDialog dialog = new JDialog(this, resources
                .getString("dialog.help.ttitle"));
        final JEditorPane helpText = new JEditorPane();
       helpText.setText("Turret\tBase Damage\tBase Range\tCooldown\tCost\n" +
       					"Turret\t15\t2\t1\t480\n" +
       					"Cannon\t30\t1\t2\t960\n" +
       					"Sniper\t10\tEntire Map\t3\t600\n" +
       					"Spray\t12\t1\t1\t840\n" +
       					"Super Turret\tInstant Kill\t2\t5\t10000\n\n" +
       					"Obstruction\tBase Damage\tLife\tCost\n" +
       					"Normal\t5\t5\t100\n" +
       					"Strong\t15\t25\t400\n\n" +
       					"Sell price: Always 50% of ALL money paid for purchase (including upgrades)\n" +
       					"Upgrade price: 25%/50%/75% of base price");
       	helpText.setEditable(false);
        JScrollPane sp = new JScrollPane(helpText);
        sp.setPreferredSize(new Dimension(500, 250));
        dialog.getContentPane().add(sp);
        dialog.setLocation(getX() + getWidth() - 200, getY() + 50);
        dialog.pack();
        dialog.setVisible(true);
    }
    private void showHelp()
    {
        JDialog dialog = new JDialog(this, resources
                .getString("dialog.help.title"));
        final JEditorPane helpText = new JEditorPane();
        helpText.setText("NOTE: Please do not meddle with the following methods: moveTo, putSelfInGrid, and removeSelfFromGrid" +
        		"\nGUIDELINES\nThroughout the game, different waves of enemies will travel down the path.\n" +
        		"The point of the game is to stop the enemies before they reach the end.\n"+
        		"To do this one must create turrets, and upgrade them to stop the enemies.\n"+
        		"Killing Enemies gives money to buy more turrets, and upgrade them.\n\n" +
        		
        		"LORE\n" +
        		"\tPokemon have always lived in harmony with people. That was before Dr. Drago arrived.\n" +
        		"He showed the pokemon how they were equivalent to slaves. They fought for us and recieved\n" +
        		"nothing in return. Almost at once the pokemon surged upon our towns, slaughtering civilians.\n" +
        		"It was genocide. However, once our army was able to organize, we were able to beat the pokemon\n" +
        		"back into their dens.\n" +
        		"\tDr. Drago was furious. This time he realized he needed a full-fledged, organized army to win.\n" +
        		"He set up an ambush in our Capital, Pika Pika, and attacked. Our army destroyed, he let his army loose\n" +
        		"on the rest of humanity.\n" +
        		"\tNow your are merely a survivor. You do not know how many more people are still alive, if any. You \n" +
        		"must survive. You have built up a fortress, and now must defend againt Dr. Drago's endless pokemon army.\n\n" +
        		
        		"ENEMIES\n" +
        		"Enemy: This is the foot soldier of the pokemon army, the charmander. While alone he is an easy kill,\n" +
        		"in large numbers the charmander is a formidable foe.\n" +
        		"FastEnemy: This is the pikachu. A pikachu can move twice as fast as a charmander, but also has half the\n" +
        		"charmander's health. Slightly more scarce than charmanders, and are a higher rank in the pokemon army.\n" +
        		"SuperFastEnemy: The gyrados' have been genetically mutated to move at amazing speeds. They move five\n" +
        		"times the speed of the charmander, but have a fraction of its health. They are the officers of the \n" +
        		"pokemon's army.\n" +
        		"TankEnemy: The snorlax. It's drowsiness is a lure to let it's opponents become unsuspecting. It has\n" +
        		"an immense amount of health, and only takes half damage. Fortunately, it is slow, and the pokemon army\n" +
        		"do not have a surplus of snorlax, otherwise you would be in a heap of trouble.\n" +
        		"BossEnemy: The pokemon army has been recreating mew! Slowly they are creating clones, the mewtwo's.\n" +
        		"These mewtwo's have an obscene amount of health, and are the generals of the pokemon army. Defeating\n" +
        		"them is a great challenge.\n\n" +
        		
        		"TIPS\n" +
        		"- The color of the enemy represents it's current percentage of its max health. Green is healthiest, then\n" +
        		"cyan, yellow, orange, and red follow in descending order." +
        		"- It's okay if you lose a life or two. You have 50 of them. You are bound to have a mutant gyrados sneak\n" +
        		"by you.\n" +
        		"- Spread out your turrets. It is a lot easier for a gyrados to jump beyond your turrets if they are all together.\n" +
        		"- Set up a defensive line of turrets. You are bound to let a few pokemon slip by, and there is no need to lose a life\n" +
        		"when you don't have to.\n" +
        		"- Early on you will be especially tight on money. Make sure to carefully think of where to place your turret's to \n" +
        		"hold the highest strategic advantage.\n" +
        		"- There is no break in between waves. Make sure to stay focused and on task, or else you will be overrun before you \n" +
        		"can say 'Pikachu!'\n" +
        		"- Turrets are the beginner's turret. With just your starting coin you can buy 4 of them, more than any other turret.\n" +
        		"Also, if you ever get strapped for cash, they are dirt cheap.\n" +
        		"- Place snipers in the far reaches of the map. They can hit anything on the map, so there is no need to have them right\n" +
        		"on the line.\n" +
        		"- Obstructions are dirt cheap, and are very useful as a last line of defense.\n" +
        		"- Place sprays in corners. More bang for your buck.\n" +
        		"- Multiple enemies can be on top of each other: some may be invisible until the one above it dies.\n" +
        		"- The game is intended to be endless, although there is some incentive to reach at least level 50.");
        helpText.setEditable(false);
        helpText.addHyperlinkListener(new HyperlinkListener()
        {
            public void hyperlinkUpdate(HyperlinkEvent ev)
            {
                if (ev.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
                    try
                    {
                        helpText.setPage(ev.getURL());
                    }
                    catch (Exception ex)
                    {
                    }
            }
        });
        JScrollPane sp = new JScrollPane(helpText);
        sp.setPreferredSize(new Dimension(700, 600));
        dialog.getContentPane().add(sp);
        dialog.setLocation(getX() + getWidth() - 200, getY() + 50);
        dialog.pack();
        dialog.setVisible(true);
    }

    /**
     * Brings up a dialog that displays the license.
     */
    private void showLicense()
    {
        JDialog dialog = new JDialog(this, resources
                .getString("dialog.license.title"));
        final JEditorPane text = new JEditorPane();
        text.setText("Devin Bornstein\nMark Westerhoff\nJohn Breen");
        text.setEditable(false);
        JScrollPane sp = new JScrollPane(text);
        sp.setPreferredSize(new Dimension(200, 100));
        dialog.getContentPane().add(sp);
        dialog.setLocation(getX() + getWidth() - 200, getY() + 50);
        dialog.pack();
        dialog.setVisible(true);
    }

    /**
     * Nested class that is registered as the handler for exceptions on the
     * Swing event thread. The handler will put up an alert panel and dump the
     * stack trace to the console.
     */
    public class GUIExceptionHandler
    {
        public void handle(Throwable e)
        {
            /*e.printStackTrace();

            JTextArea area = new JTextArea(10, 40);
            StringWriter writer = new StringWriter();
            e.printStackTrace(new PrintWriter(writer));
            area.setText(writer.toString());
            area.setCaretPosition(0);
            String copyOption = resources.getString("dialog.error.copy");
            JOptionPane pane = new JOptionPane(new JScrollPane(area),
                    JOptionPane.ERROR_MESSAGE, JOptionPane.YES_NO_OPTION, null,
                    new String[]
                        { copyOption, resources.getString("cancel") });
            pane.createDialog(WorldFrame.this, e.toString()).setVisible(true);
            if (copyOption.equals(pane.getValue()))
            {
                area.setSelectionStart(0);
                area.setSelectionEnd(area.getText().length());
                area.copy(); // copy to clipboard*/
            //}
        }
    }
}
